#include "tnet.h"

#include <ctype.h>  // isdigit
#include <stdarg.h>  // varargs
#include <string.h>  // strlen
#include <stdlib.h>  // malloc/free


// INTERNAL

// log diagnostic error  text
static void tnet_log(TnetContext* ctx, const char* fmt, ...) {
  va_list argptr;
  va_start(argptr, fmt);
  // TODO: conform with PGI logging spec?  It starts with a space now.
  fputs(" tnet: ", ctx->logfd);
  vfprintf(ctx->logfd, fmt, argptr);
  fputs("\n", ctx->logfd);
  va_end(argptr);
}

static void tnet_debug(const char* fmt, ...) {
  va_list argptr;
  va_start(argptr, fmt);
  // TODO: conform with PGI logging spec?  It starts with a space now.
  fputs(" tnet: ", stderr);
  vfprintf(stderr, fmt, argptr);
  fputs("\n", stderr);
  va_end(argptr);
}

// length of an integer as a string
static int len_len(int value){
  int len = 1;  // 0 has length 1
  while (value >= 10) {
    len++;
    value /= 10;
  }
  return len;
}


// HELPER FUNCTIONS

// Print a buffer
size_t tnet_print_buf(Buffer b, FILE* f) {
  return fwrite(b.data, sizeof(char), b.len, f);
}

/**
 * Print Pairs of chunks to a file.
 */
void tnet_print_pairs(Pair* p, FILE* f) {
  if (p) {
    tnet_debug("key: ");
    tnet_print_buf(p->key.payload, f);
    fputs("\n", stderr);
    tnet_debug("value: ");
    tnet_print_buf(p->value.payload, f);
    fputs("\n", stderr);

    tnet_print_pairs(p->next, f);
  }
}

// Create a Chunk from a char* string.
Chunk tnet_string_to_chunk(char* s) {
  Chunk c;
  c.tag = ',';
  c.payload.data = s;
  c.payload.len = strlen(s);
  return c;
}


// CLEANUP FUNCTIONS

// Free a linked list of Pairs.  This needs to be called on the return value of
// tnet_parse_dict.

void tnet_free_pairs(Pair* p) {
  if (p) {
    tnet_free_pairs(p->next);
    free(p);
  }
}

void tnet_free_buffer(Buffer b) {
  free(b.data);
}

void tnet_free_chunk(Chunk c) {
  free(c.payload.data);
}

int tnet_chunk_equals_string(Chunk c, const char* s) {
  int len = strlen(s);
  if (len != c.payload.len) {
    return 0;
  }

  char* p = c.payload.data;
  while (*s) {
    if (*s++ != *p++) {
      //tnet_log("NOT EQUAL %c %c", a, b); 
      return 0;
    }
  }
  return 1;
}

// get the value associated with a name in the pair
// the name is a char* for convenience
int tnet_pairs_getchunk(Pair* p, const char* name, Chunk* c) {
  if (p) {
    //tnet_log("KEY SEARCH");
    //print_buf(p->key.payload);
    if (tnet_chunk_equals_string(p->key, name)) {
      *c = p->value;
      return 1;
    }
    return tnet_pairs_getchunk(p->next, name, c);
  }
  return 0;
}

// End address of Buffer
static char* end_addr(Buffer b) {
  return b.data + b.len;
}

// Load a chunk from the current position in the buffer.  Fills the chunk.
// Mutates pp.
static int parse_chunk_prefix(TnetContext* ctx, Buffer* buf, Chunk* chunk, char** pp) {
  char* p = *pp;
  int len;

  sscanf(p, "%9d", &len);  /* >999999999 bytes is bad */
  p += len_len(len);;

  if (*p != ':') {
    tnet_log(ctx, "Expected : but got %c", *p);
    return 0;
  }
  p++;

  // The output chunk buffer points at the character after ':' inside the input
  // buffer.
  chunk->payload.data = p;
  chunk->payload.len = len;

  // Check that we didn't read a length that goes past the end of the buffer.
  // Then we'd be pointing to unitialized memory.
  if (end_addr(chunk->payload) > end_addr(*buf)) {
    tnet_log(ctx, "Buffer overflow");
    return 0;
  }
  
  p += len;
  chunk->tag = *p;
  p++;  // advance past tag

  //tnet_log("chunk tag %c", chunk->tag);
  //tnet_log("char %c", *p);

  *pp = p;  // move the pointer
  return 1;
}

// Parse a Buffer into an array of pairs of chunks.  If the dictinonary is
// empty, pairs = NULL.  Otherwise it is a linked list of Pairs.
//
// Allocates memory for buffers underlying Pairs, caller must call
// tnet_free_pairs afterward.
int tnet_parse_dict_chunks(TnetContext* ctx, Buffer buf, Pair** pairs) {
  // parse prefix in a loop.
  // Each time, malloc a Pair and push onto it.

  Pair* head = NULL;  // empty dict gets null
  Pair* cur = head;  // current pair
  Pair* prev = NULL;

  char* p = buf.data;  // moves through the buffer
  char* end = end_addr(buf);

  while (p < end) {
    //tnet_debug("BUF LEN: %d", buf.len);

    // allocate Pair
    Pair* cur = (Pair*)ctx->malloc(sizeof(Pair));
    if (!cur) {
      tnet_log(ctx, "malloc failed");
      return 0;
    }
    if (!head) {
      head = cur;  // save first one
    }

    // terminate it
    cur->next = NULL;

    // link it
    if (prev) {
      prev->next = cur;
    }

    if (!parse_chunk_prefix(ctx, &buf, &cur->key, &p)) {
      tnet_log(ctx, "Parsing dict key failed.");
      return 0;
    }

    if (!parse_chunk_prefix(ctx, &buf, &cur->value, &p)) {
      tnet_log(ctx, "Parsing dict value failed.");
      return 0;
    }
    prev = cur;
  }

  *pairs = head;
  return 1;
}

// Hm you know all these sizes.
//
// Allocates memory for 'out'; caller is responsible for tnet_free_buffer(out)
// after.
int tnet_dump_dict(TnetContext* ctx, Pair* pairs, Buffer* out) {
  // Go through all the sizes in the pairs.  Then allocate one big buffer, since
  // it's not recursive.

  // total length is:
  // sum of lengths
  // 2 x number of keys+values -- for : and ,
  // sum of key lengths and value lengths

  int payload_len = 0;
  Pair* p = pairs;
  while (p) {
    int key_len = p->key.payload.len;
    payload_len += key_len;
    payload_len += len_len(key_len);

    //tnet_print_buf(p->value.payload, stderr);

    int value_len = p->value.payload.len;
    payload_len += value_len;
    payload_len += len_len(value_len);

    payload_len += 4;  // for : and tag in key, and then in value

    p = p->next;
  }

  // 2 for : and }
  int buf_length = payload_len + len_len(payload_len) + 2;

  char* data = (char*)ctx->malloc(buf_length + 1);  /* malloc(0) */
  if (!data) {
    tnet_log(ctx, "malloc failed");
    return 0;
  }

  p = pairs;  // back to the beginning
  char* d = data;

  d += sprintf(d, "%d:", payload_len);

  while (p) {
    Chunk* key = &p->key;
    Chunk* value = &p->value;

    int key_len = key->payload.len;

    // Write length and :, and advance pointer.
    d += sprintf(d, "%d:", key_len);

    // Write payload and tag
    memcpy(d, key->payload.data, key_len);
    d += key_len;
    *d++ = key->tag;

    int value_len = value->payload.len;

    // Write length and :
    d += sprintf(d, "%d:", value_len);

    //tnet_print_buf(value->payload, stderr);

    // Write payload and tag
    memcpy(d, value->payload.data, value_len);
    d += value_len;
    *d++ = value->tag;

    p = p->next;
  }
  *d = '}';  // ending

  out->data = data;
  out->len = buf_length;

  return 1;
}

#define MAX_LEN_LEN 9  // max 9 char length

// Read a chunk from a file handle.
// Allocates memory for the Chunk, caller must call tnet_free_chunk() afterward.
int tnet_read(TnetContext* ctx, FILE* f, Chunk* c) {
  // NOTE: this loop used to be fscanf, but that doesn't appear to block?
  char ch = 0;
  int len = 0;
  int i;
  for (i = 0; i < MAX_LEN_LEN; ++i) {
    // Read the i'th character from f
    if (fread(&ch, 1, 1, f) != 1) {
      tnet_log(ctx, "EOF (i = %d)", i);
      return 0;
    }
    if (ch == ':') {
      break;
    }
    if (!isdigit(ch)) {
      tnet_log(ctx, "Expected an an integer, got '%c'", ch);
      return 0;
    }
    len = 10 * len + (ch - '0');
  }

  char* data = (char*)ctx->malloc(len + 1);  /* malloc(0) is not portable */
  if (!data) {
    tnet_log(ctx, "malloc failed");
    return 0;
  }
  if (fread(data, 1, len, f) < len) return 0;
  char tag = fgetc(f);

  c->tag = tag;
  c->payload.len = len;
  c->payload.data = data;
  return 1;
}
